package nancal.mp.service.git.impl

import nancal.mp.db.mongo.entity.dev.GitServer
import nancal.mp.extend.toBjTime
import nancal.mp.model.git.GitBranchCommitModel
import nancal.mp.model.git.GitFileItem
import nancal.mp.model.git.GitOrgItem
import nancal.mp.service.git.GitApiService
import nbcp.base.comm.*
import nbcp.base.db.IdName
import nbcp.base.enums.HttpMethod
import nbcp.base.extend.*
import nbcp.base.model.HttpInvoke
import nbcp.base.utils.CodeUtil
import nbcp.base.utils.FileUtil
import nbcp.base.utils.ZipUtil
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
import java.io.File
import java.nio.charset.StandardCharsets
import java.time.LocalDateTime

@Service
class GitEaService : GitApiService {
    companion object {
        private val logger = LoggerFactory.getLogger(this::class.java.declaringClass)
    }

    /**
     * 获取组织
     */
    override fun getOrgs(gitServer: GitServer): ListResult<IdName> {
        var url =
            "${gitServer.host}/api/v1/orgs?access_token=${gitServer.accessToken}&limit=50&page="

        var page = 0;
        var list = mutableListOf<IdName>()
        while (true) {
            page++;
            var http = HttpInvoke();
            http.url = (url + page);
            http.requestMethod = "GET"
            var res = http.doInvoke()
            if (res.isError) {
                return ListResult.error("获取Git组织信息失败")
            }

            var items = res.responseText!!.FromListJson(JsonMap::class.java)
                //组织名，在 username 字段
                .map {
                    IdName(
                        it.get("id").AsString(),
                        it.get("username").AsString()
                    )
                }

            list.addAll(items)

            if (items.size < 50) {
                break;
            }
        }

        return ListResult.of(list)
    }


    override fun getRepos(gitServer: GitServer, org: String): ListResult<GitOrgItem> {
        var url =
            "${gitServer.host}/api/v1/orgs/${org}/repos?access_token=${gitServer.accessToken}&limit=50&page="

        var page = 0;
        var list = mutableListOf<GitOrgItem>()
        while (true) {
            page++;
            var http = HttpInvoke()
            http.url = (url + page);
            http.requestMethod = "GET"

            var res = http.doInvoke()
            if (res.isError) {
                return ListResult.error("获取Git组织信息失败")
            }

            var items = res.responseText!!.FromListJson(JsonMap::class.java)
                .map {
                    GitOrgItem(
                        it.get("id").AsString(),
                        it.get("name").AsString(),
                        it.get("full_name").AsString(),
                        it.get("clone_url").AsString(),
                        it.get("archived").AsBoolean(),
                        it.get("size").AsLong()
                    )
                }

            list.addAll(items)
            if (items.size < 50) {
                break;
            }
        }

        return ListResult.of(list)
    }


    fun createFile(
        gitServer: GitServer,
        owner: String,
        repo: String,
        branch: String,
        fileName: String,
        committerName: String,
        message: String,
        content: String
    ): JsonResult {
        var url =
            "${gitServer.host}/api/v1/repos/${owner}/${repo}/contents/${fileName}?access_token=${gitServer.accessToken}"
        var postBody = JsonMap();
        postBody.put("branch", branch)
        postBody.put("content", content)
        postBody.put("committer", JsonMap("name" to committerName))
        postBody.put("message", message.AsString("by mp-integration"))

        var http = HttpInvoke()
        http.url = (url);
        http.postObject = postBody
        var res = http.doInvoke()
        if (res.isError) {
            return JsonResult.error("更新文件失败")
        }

        return JsonResult()
    }

    fun updateFile(
        gitServer: GitServer,
        owner: String,
        repo: String,
        branch: String,
        fileName: String,
        committerName: String,
        message: String,
        content: String,
        prev_sha: String
    ): JsonResult {
        var url =
            "${gitServer.host}/api/v1/repos/${owner}/${repo}/contents/${fileName}?access_token=${gitServer.accessToken}"
        var postBody = JsonMap();
        postBody.put("branch", branch)
        postBody.put("content", content)
        postBody.put("committer", JsonMap("name" to committerName))
        postBody.put("message", message.AsString("mp-integration"))
        postBody.put("sha", prev_sha)
        var http = HttpInvoke()
        http.url = (url);
        http.postObject = postBody
        var res = http.doInvoke()

        if (res.isError) {
            return JsonResult.error("更新文件失败")
        }

        return JsonResult()
    }

    /**
     * 获取分支下指定文件的 Sha
     */
    fun getFileMetaDataSha(
        gitHost: String,
        accessToken: String,
        owner: String,
        repo: String,
        branch: String,
        fileName: String
    ): String {
        var url =
            "${gitHost}/api/v1/repos/${owner}/${repo}/contents/${fileName}?ref=${branch}&access_token=${accessToken}"
        var http = HttpInvoke()
        http.url = (url);
        http.requestMethod = "GET"
        var res = http.doInvoke()
        if (res.isSuccess) {
            return res.responseText.FromJson<JsonMap>()?.get("sha").AsString()
        }
        if (res.status == 404) {
            return ""
        }
        throw RuntimeException("获取文件出错:${res.responseText}")
    }


    /**
     * 获取 分支信息
     */
    override fun getBranchCommitInfo(
        gitHost: String,
        accessToken: String,
        owner: String,
        repo: String,
        branch: String
    ): ApiResult<GitBranchCommitModel> {
        var url =
            "${gitHost}/api/v1/repos/${owner}/${repo}/branches/${branch}?access_token=${accessToken}"
        var http = HttpInvoke()
        http.url = (url);
        http.requestMethod = "GET"
        var res = http.doInvoke()
        if (res.isError) {
            throw RuntimeException("找不到Git分支信息")
        }
        var commitMap = res.responseText!!.FromJson<JsonMap>()!!

        var commitInfo = GitBranchCommitModel();
        commitInfo.commitId = commitMap.getStringValue("commit.id").AsString()
        commitInfo.commitMessage = commitMap.getStringValue("commit.message").AsString()
        commitInfo.committerName = commitMap.getStringValue("commit.committer.name")
            .AsString({ commitMap.getStringValue("commit.committer.username").AsString() })
        commitInfo.commitAt = commitMap.getStringValue("commit.timestamp").AsLocalDateTime()
        return ApiResult.of(commitInfo);
    }


    override fun getRawFile(
        gitHost: String,
        accessToken: String,
        owner: String,
        repo: String,
        branch: String,
        fileName: String
    ): ApiResult<ByteArray> {

        var url =
            "${gitHost}/api/v1/repos/${owner}/${repo}/raw/${fileName}?ref=${branch}&access_token=${accessToken}"
        var http = HttpInvoke()
        http.url = (url);
        http.requestMethod = "GET"
        var res = http.doInvoke()
        if (res.isSuccess) {
            return ApiResult.of(res.responseText?.toByteArray(StandardCharsets.UTF_8))
        }
        if (res.status == 404) {
            return ApiResult.error("路径错误", code = 404);
        }

        return ApiResult.error("错误:${res.responseText}")
    }

    fun deleteFile(
        gitHost: String,
        accessToken: String,
        owner: String,
        repo: String,
        branch: String,
        fileName: String,
        committerName: String,
        message: String
    ): ApiResult<String> {
        var url =
            "${gitHost}/api/v1/repos/${owner}/${repo}/contents/${fileName}?access_token=${accessToken}"
        var http = HttpInvoke()
        http.url = (url);

        val sha = getFileMetaDataSha(gitHost, accessToken, owner, repo, branch, fileName)

        val postBody = JsonMap();
        postBody.put("branch", branch)
        postBody.put("committer", JsonMap("name" to committerName))
        postBody.put("message", message.AsString("by mp-integration"))
        postBody.put("sha", sha)

        http.requestMethod = HttpMethod.DELETE.name
        http.postString = postBody.ToJson()

        var res = http.doInvoke();

        if (res.isSuccess) {
            return ApiResult.of(res.responseText)
        }

        return ApiResult.error("错误:${res.status}")
    }


    override fun getBranches(
        gitHost: String,
        accessToken: String,
        owner: String,
        repo: String,
        take: Int
    ): ListResult<GitBranchCommitModel> {
        var total = -1L;
        var page = 1;
        var limit = 36; // 最大数是50

        var list = mutableListOf<GitBranchCommitModel>()
        for (i in 0..9) {
            var ret = getBranches(gitHost, accessToken, owner, repo, page, limit);
            if (ret.msg.HasValue) {
                return ListResult.error(ret.msg);

            }
            list.addAll(ret.data);
            total = ret.total;

            if (list.size >= total) {
                break;
            }

            page++;
        }

        list.sortByDescending { it.commitAt }

        list.forEach {
            it.commitAt = it.commitAt?.toBjTime()
        }

        return ListResult.of(list.Slice(0, if (take <= 0) 36 else take), total);
    }

    override fun existsFile(
        gitHost: String,
        accessToken: String,
        owner: String,
        repo: String,
        branchName: String,
        path: String
    ): ApiResult<Boolean> {
        var sha = this.getFileMetaDataSha(gitHost, accessToken, owner, repo, branchName, path)
        if (sha.HasValue) {
            return ApiResult.of(true)
        }

        return ApiResult.of(false)
    }

    private fun getBranches(
        gitHost: String,
        accessToken: String,
        owner: String,
        repo: String,
        page: Int,
        limit: Int
    ): ListResult<GitBranchCommitModel> {
        var url =
            "${gitHost}/api/v1/repos/${owner}/${repo}/branches?access_token=${accessToken}&limit=${
                if (limit.IfRangeTo(
                        1,
                        50
                    )
                ) limit else 50
            }&page=${
                if (page <= 0) 1 else page
            }"
        var http = HttpInvoke()
        http.url = (url);
        http.requestMethod = "GET"
        var res = http.doInvoke()
        if (res.isError) {
            return ListResult.error("Git请求错误", res.status)
        }

        var gitFileData = res.responseText!!;

        var list = gitFileData.FromListJson(JsonMap::class.java).map { branch ->
            var item = GitBranchCommitModel();
            item.name = branch.get("name").AsString()
            val commitJson = branch.get("commit") as Map<String, *>;
            item.commitId = commitJson.get("id").AsString()
            item.commitMessage = commitJson.get("message").AsString().trim();

            val committer = commitJson.get("committer") as Map<String, *>
            item.committerName =
                committer.get("name").AsString(committer.get("username").AsString());
            item.commitAt = commitJson.get("timestamp").AsString().AsLocalDateTime()
            return@map item;
        }

        return ListResult.of(list, res.responseHeader.get("x-total-count").AsLong())
    }


    fun listFiles(
        gitHost: String,
        accessToken: String,
        owner: String,
        repo: String,
        branch: String,
        filePath: String = "", //从头开始的文件名，头部不以 / 开头。
    ): ListResult<GitFileItem> {
        val commitInfo = getBranchCommitInfo(gitHost, accessToken, owner, repo, branch);
        var page = 1;
        var per_page = 500;
        var total_count = 0;
        var list = mutableListOf<GitFileItem>()
        while (true) {
            var map = StringMap(
                "recursive" to "true",
                "access_token" to accessToken,
                "page" to page.toString(),
                "per_page" to per_page.toString()
            );

            var url =
                "${gitHost}/api/v1/repos/${owner}/${repo}/git/trees/${commitInfo.data!!.commitId}"
            var http = HttpInvoke()
            http.url = (url)
            http.requestMethod = "GET"
            var res = http.doInvoke()
            if (res.isError) {
                return ListResult.error("错误:${res.status}")
            }

            var gitFileData = res.responseText!!;

            var gitJson = gitFileData.FromJson<JsonMap>()!!;

            total_count = gitJson.get("total_count").AsInt()

            if (total_count == 0) {
                break;
            }

            if (gitJson.get("tree") == null) {
                break;
            }

            val data = (gitJson.get("tree") as List<Map<String, Any?>>).map {
                return@map GitFileItem(
                    it.get("type").AsString() == "blob",
                    it.get("path").AsString(),
                    it.get("sha").AsString()
                )
            }

            list.addAll(data);

            if (list.size >= total_count) {
                break;
            }
        }

        if (filePath.HasValue) {
            var filePath2 = if (filePath.startsWith('/')) filePath.Slice(1) else filePath
            if (filePath2.HasValue) {
                list = list.filter { it.fileName.startsWith(filePath2) }.toMutableList()
            }
        }

        return ListResult.of(list.filter { it.isFile })
    }


    /**
     * 生成源码包，耗时可能会比较长。
     * @return 返回 Zip包的流。
     */
    fun cloneZipFile(
        gitHost: String,
        accessToken: String,
        owner: String,
        repo: String,
        branch: String
    ): ApiResult<String> {
        //清除24小时之前的临时文件
        clearTmp(-24)

        var code = CodeUtil.getCode();

        var path = FileUtil.resolvePath(
            System.getProperty("java.io.tmpdir"),
            "mp-${code}-${LocalDateTime.now().Format("yyyyMMddHHmmss")}",
            code + ".zip"
        )


        var file = File(path)
        file.parentFile.mkdirs()
        var zipFile = ZipUtil.beginCompress(file)

        this.listFiles(gitHost, accessToken, owner, repo, branch)
            .apply {
                if (this.msg.HasValue) {
                    return ApiResult.error(this.msg)
                }

                this.data.forEach { fileName ->
                    var fileContent = getRawFile(gitHost, accessToken, owner, repo, branch, fileName.fileName)

                    if (fileContent.msg.HasValue) {
                        return ApiResult.error(fileContent.msg);
                    }

                    fileContent.data!!.inputStream().use {
                        zipFile.addFile(it, fileName.fileName)
                    }
                }
            }

        return ApiResult.of(path);
    }

    private fun clearTmp(diffHours: Int) {
        var now = LocalDateTime.now();
        File(System.getProperty("java.io.tmpdir")).listFiles()
            .filter {
                if (it.isFile) return@filter false;
                if (it.name.startsWith("mp-") == false) {
                    return@filter false
                }

                var sects = it.name.split('-');

                if (sects.last().AsLocalDateTime()!!.minusHours(diffHours.toLong()) < now) {
                    return@filter true;
                }
                return@filter false;
            }
            .forEach {
                it.deleteRecursively()
            }
    }
}
